#ifndef GRAPH_H
#define GRAPH_H

#include <map>
#include "Common/Exceptions/NotFoundException.h"

namespace cmn {

	template<typename T>
	class Graph
	{
	public:
		enum class GraphType { Directed, Undirected };

	private:
		typedef std::map<const T, bool > RowMap;
		typedef std::map<const T, RowMap* > TableMap;

		TableMap m_table;
		Vector<T> m_nodes;
		GraphType m_type;

	public:
		Graph( GraphType type ) :
			m_type( type )
		{}

		void AddNode( const T& node ) {
			if( ContainsNode( node ) )
				throw NotFoundException( "Node not found" );

			RowMap* newRow = new RowMap();

			for( IteratorPtr<T> nodes( &m_nodes ); !nodes.IsEnd(); nodes.Next() ) {
				T item = nodes.Current();
				RowMap* row = m_table[item];
				( *row )[node] = false;
				( *newRow )[item] = false;
			}

			( *newRow )[node] = false;
			m_table[node] = newRow;
			m_nodes.Add( node );
		}

		void RemoveNode( const T& node ) {
			if( !ContainsNode( node ) )
				throw NotFoundException( "Node not found" );

			for( IteratorPtr<T> nodes( &m_nodes ); !nodes.IsEnd(); nodes.Next() ) {
				T item = nodes.Current();
				m_table[item]->erase( node );
			}

			m_table.erase( node );
			m_nodes.Remove( node );
		}

		bool ContainsNode( const T& node ) {
			return m_nodes.Contains( node );
		}

		bool IsLinked( const T& nodeA, const T& nodeB ) {
			if( !ContainsNode( nodeA ) || !ContainsNode( nodeB ) )
				throw NotFoundException( "Node not found to check link" );

			return m_table.at( nodeA )->at( nodeB );
		}

		void Link( const T& nodeA, const T& nodeB )	{
			SetLinked( nodeA, nodeB, true );
		}

		void Unlink( const T& nodeA, const T& nodeB ) {
			SetLinked( nodeA, nodeB, false );
		}

		void SetLinked( const T& nodeA, const T& nodeB, bool linked ) {
			if( !ContainsNode( nodeA ) || !ContainsNode( nodeB ) )
				throw NotFoundException( "Node not found to set link" );

			switch( m_type ) {
				case GraphType::Directed:
					m_table.at( nodeA )->at( nodeB ) = linked;
					return;
				case GraphType::Undirected:
					m_table.at( nodeA )->at( nodeB ) = linked;
					m_table.at( nodeB )->at( nodeA ) = linked;
					return;
			}
		}

		const IIterable<T>* GetNodes() const {
			return &m_nodes;
		}

		const IIterable<T>* GetNeihgbors( T& node ) {
			if( !ContainsNode( node ) )
				throw NotFoundException( "Node not found to find neighbor" );

			Vector<T>* neighbors = new Vector<T> ();
			RowMap* row = m_table[node];

			for( IteratorPtr<T> nodes( &m_nodes ); !nodes.IsEnd(); nodes.Next() ) {
				T item = nodes.Current();
				if( row->at( item ) )
					neighbors->Add( item );
			}
			return neighbors;
		}
	};
}
#endif
